#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include "defs.h"
#include "externs.h"
#include "protos.h"

struct t_func *func_tbl[256];
struct t_func *func_ptr;
char func_line[128];
char func_arg[8][10][80];
int  func_idx;


/* ----
 * do_func()
 * ----
 * .func pseudo
 */

void
do_func(int *ip)
{
    if (pass == LAST_PASS)
        println();
    else {
        /* error checking */
        if (lablptr == NULL) {
            error("No name for this function!");
            return;
        }
        if (lablptr->refcnt) {
            switch (lablptr->type) {
            case MACRO:
                fatal_error("Symbol already used by a macro!");

            case FUNC:
                fatal_error("Function already defined!");
                return;

            default:
                fatal_error("Symbol already used by a label!");
                return;
            }
        }

        /* install this new function in the hash table */
        if (!func_install(*ip))
            return;
    } 
}

/* search a function */

int
func_look(void)
{
    int hash;

    /* search the function in the hash table */
    hash = symhash();
    func_ptr = func_tbl[hash];
    while (func_ptr) {
        if (!strcmp(&symbol[1], func_ptr->name))
            break;            
        func_ptr = func_ptr->next;
    }

    /* ok */
    if (func_ptr)
        return (1);

    /* didn't find a function with this name */
    return (0);
}

/* install a function in the hash table */

int
func_install(int ip)
{
    int hash;

    /* mark the function name as reserved */
    lablptr->type = FUNC;

    /* check function name syntax */
    if (strchr(&symbol[1], '.')) {
        error("Invalid function name!");
        return (0);
    }

    /* extract function body */
    if (func_extract(ip) == -1)
        return (0);

    /* allocate a new func struct */
    if ((func_ptr = (void *)mem_alloc(sizeof(struct t_func))) == NULL) {
        error("Out of memory!");
        return (0);
    }

    /* initialize it */
    strcpy(func_ptr->name, &symbol[1]);
    strcpy(func_ptr->line, func_line);
    hash = symhash();
    func_ptr->next = func_tbl[hash];
    func_tbl[hash] = func_ptr;

    /* ok */
    return (1);
}

/* extract function body */

int
func_extract(int ip)
{
    char *ptr;
    char  c;
    int   i, arg, max_arg;
    int   end;

    /* skip spaces */
    while (isspace(prlnbuf[ip]))
        ip++;

    /* get function body */
    ptr = func_line;
    max_arg = 0;
    end = 0;
    i = 0;

    while (!end) {
        c = prlnbuf[ip++];
        switch (c) {
        /* end of line */    
        case ';':
        case '\0':
           *ptr++ = '\0';
            end = 1;
            break;

        /* function arg */
        case '\\':
           *ptr++ = c;
            i++;
            c = prlnbuf[ip++];
            if ((c < '1') || (c > '9')) {
                error("Invalid function argument!");
                return (-1);
            }
            arg = c - '1';
            if (max_arg < arg)
                max_arg = arg;

        /* other */
        default:
           *ptr++ = c;
            i++;
            if (i == 127) {
                error("Function line too long!");
                return (-1);
            }
            break;
        }
    }

    /* return the number of args */
    return (max_arg);
}

/* extract function args */

int
func_getargs(void)
{
    unsigned char c, *ptr, *line;
    int arg, level, space, flag;
    int i, x;

    /* can not nest too much macros */
    if (func_idx == 7) {
        error("Too many nested function calls!");
        return (0);
    }

    /* skip spaces */
    while (isspace(*expr))
        expr++;

    /* function args must be enclosed in parenthesis */
    if (*expr++ != '(')
        return (0);

    /* initialize args */
    line = NULL;
    ptr  = func_arg[func_idx][0];
    arg  = 0;

    for (i = 0; i < 9; i++)
        func_arg[func_idx][i][0] = '\0';

    /* get args one by one */
    for (;;) {
        /* skip spaces */
        while (isspace(*expr))
            expr++;

        c = *expr++;
        switch (c) {
        /* empty arg */
        case ',':
            arg++;
            ptr = func_arg[func_idx][arg];
            if (arg == 9) {
                error("Too many arguments for a function!");
                return (0);
            }
            break;

        /* end of line */    
        case ';':
        case '\0':
            error("Syntax error in function call!");
            return (0);

        /* end of function */
        case ')':
            return (1);

        /* arg */
        default:
            space = 0;
            level = 0;
            flag = 0;
            i = 0;
            x = 0;
            for (;;) {
                if (c == '\0') {
                    if (flag == 0)
                        break;
                    else {
                        flag = 0;
                        c = *expr++;
                        continue;
                    }
                }
                else if (c == ';') {
                    break;
                }
                else if (c == ',') {
                    if (level == 0)
                        break;
                }
                else if (c == '\\') {
                    if (func_idx == 0) {
                        error("Syntax error!");
                        return (0);
                    }
                    c = *expr++;
                    if (c < '1' || c > '9') {
                        error("Invalid function argument index!");
                        return (0);
                    }
                    line = func_arg[func_idx - 1][c - '1'];
                    flag = 1;
                    c = *line++;
                    continue;
                }
                else if (c == '(') {
                    level++;
                }
                else if (c == ')') {
                    if (level == 0)
                        break;
                    level--;
                }
                if (space) {
                    if (c != ' ') {
                        while (i < x)
                            ptr[i++] = ' ';
                        ptr[i++] = c;
                        space = 0;
                    }
                }
                else if (c == ' ') {
                    space = 1;
                }
                else {
                    ptr[i++] = c;
                }
                if (i == 80) {
                    error("Invalid function argument length!");
                    return (0);
                }
                x++;
                if (flag)
                    c = *line++;
                else
                    c = *expr++;
            }
            ptr[i] = '\0';
            expr--;
            break;
        }
    }
}

